---
title: 模板和泛型编程
---

模板是 C++ 中一个强大的功能，它允许你编写适用于不同数据类型的通用代码，而无需为每种类型重写相同的代码。这是一种**泛型编程**的形式，旨在编写独立于其操作的特定数据类型的算法和数据结构。虽然 JavaScript 通过其动态类型实现了一种泛型形式，但 C++ 模板提供了编译时类型安全和性能。

## 函数模板

**函数模板**定义了一系列可以操作不同数据类型的函数。编译器根据调用模板时使用的类型生成实际的函数（模板实例化）。

<UniversalEditor title="函数模板比较" compare={true}>
```javascript !! js
// JavaScript: 通用函数 (动态类型处理泛型)
function identity(value) {
  return value;
}

console.log(identity(123)); // 123
console.log(identity("hello")); // "hello"
console.log(identity(true)); // true

function add(a, b) {
  return a + b;
}

console.log(add(5, 3)); // 8
console.log(add("Hello ", "World")); // "Hello World"
```

```cpp !! cpp
// C++: 函数模板
#include <iostream>
#include <string>

// 函数模板用于 identity
template <typename T>
T identity(T value) {
  return value;
}

// 函数模板用于 add
template <typename T>
T add(T a, T b) {
  return a + b;
}

int main() {
  // std::cout << identity(123) << std::endl; // T 变为 int
  // std::cout << identity("hello") << std::endl; // T 变为 const char*
  // std::cout << identity(true) << std::endl; // T 变为 bool

  // std::cout << add(5, 3) << std::endl; // T 变为 int
  // std::cout << add(5.5, 3.2) << std::endl; // T 变为 double
  // std::cout << add(std::string("Hello "), std::string("World")) << std::endl; // T 变为 std::string

  return 0;
}
```
</UniversalEditor>

## 类模板

**类模板**定义了一系列可以存储和操作不同数据类型对象的类。这通常用于通用数据结构，如列表、栈、队列等。

<UniversalEditor title="类模板比较" compare={true}>
```javascript !! js
// JavaScript: 通用类 (动态类型)
class Box {
  constructor(value) {
    this.value = value;
  }

  getValue() {
    return this.value;
  }
}

const intBox = new Box(10);
console.log(intBox.getValue()); // 10

const stringBox = new Box("text");
console.log(stringBox.getValue()); // "text"
```

```cpp !! cpp
// C++: 类模板
#include <iostream>
#include <string>

template <typename T>
class Box {
public:
  T value;

  Box(T val) : value(val) {}

  T getValue() {
    return value;
  }
};

int main() {
  Box<int> intBox(10); // 实例化 Box with int
  // std::cout << intBox.getValue() << std::endl; // 10

  Box<std::string> stringBox("text"); // 实例化 Box with std::string
  // std::cout << stringBox.getValue() << std::endl; // "text"

  return 0;
}
```
</UniversalEditor>

## 模板特化和部分特化

有时，通用模板对于某些特定类型可能不是最佳的，甚至是不正确的。**模板特化**允许你提供一个完全不同的实现，用于特定类型。**部分特化**允许你为模板参数的子集提供特化实现。

### 完全特化

<UniversalEditor title="完全模板特化" compare={true}>
```javascript !! js
// JavaScript: 通过条件逻辑或类型检查实现
function processData(data) {
  if (typeof data === 'string') {
    return `处理字符串: ${data.toUpperCase()}`;
  } else if (typeof data === 'number') {
    return `处理数字: ${data * 2}`;
  } else {
    return `处理通用: ${data}`;
  }
}

console.log(processData("hello"));
console.log(processData(10));
```

```cpp !! cpp
// C++: 完全模板特化
#include <iostream>
#include <string>

template <typename T>
void printValue(T value) {
  // std::cout << "通用打印: " << value << std::endl;
}

// int 的完全特化
template <>
void printValue<int>(int value) {
  // std::cout << "int 的特化打印: " << value * 2 << std::endl;
}

// std::string 的完全特化
template <>
void printValue<std::string>(std::string value) {
  // std::cout << "std::string 的特化打印: " << value << " (长度: " << value.length() << ")" << std::endl;
}

int main() {
  // printValue(5); // 调用 int 特化版本
  // printValue(3.14); // 调用通用 double 版本
  // printValue(std::string("test")); // 调用 std::string 特化版本
  return 0;
}
```
</UniversalEditor>

### 部分特化 (用于类模板)

部分特化仅适用于类模板，不适用于函数模板。

<UniversalEditor title="部分模板特化" compare={true}>
```javascript !! js
// JavaScript: 没有直接等效，但可以通过不同实现实现类似逻辑
// 这是由 JavaScript 的动态特性处理的。
```

```cpp !! cpp
// C++: 部分模板特化 (用于类模板)
#include <iostream>

template <typename T1, typename T2>
class Pair {
public:
  Pair(T1 v1, T2 v2) {
    // std::cout << "通用 Pair 已创建。" << std::endl;
  }
};

// Pair<T, int> 的部分特化
template <typename T>
class Pair<T, int> {
public:
  Pair(T v1, int v2) {
    // std::cout << "部分特化 Pair<T, int> 已创建。" << std::endl;
  }
};

int main() {
  // Pair<double, char> p1(1.1, 'a'); // 调用通用 Pair
  // Pair<double, int> p2(2.2, 10); // 调用部分特化 Pair<T, int>
  return 0;
}
```
</UniversalEditor>

## 可变参数模板

**可变参数模板**允许函数或类接受任意数量的不同类型参数。这对于像 `printf` 这样的函数或创建自定义日志机制非常有用。

<UniversalEditor title="可变参数模板比较" compare={true}>
```javascript !! js
// JavaScript: 剩余参数处理可变参数
function log(...args) {
  console.log(...args);
}

log("Hello", 123, true);
log("Only one argument");
```

```cpp !! cpp
// C++: 可变参数模板
#include <iostream>

// 递归的基准情况
void log() {
  // std::cout << std::endl;
}

// 递归可变参数模板函数
template <typename T, typename... Args>
void log(T firstArg, Args... args) {
  // std::cout << firstArg << " ";
  log(args...); // 递归调用
}

int main() {
  // log("Hello", 123, true);
  // log("Only one argument");
  return 0;
}
```
</UniversalEditor>

## 模板元编程基础

**模板元编程 (TMP)** 是一种技术，其中模板用于在编译时而不是运行时执行计算。这可以产生高度优化的代码，但它也可能复杂且难以调试。

常见用途包括：
*   编译时计算（例如，阶乘、斐波那契数列）。
*   类型特性（查询类型的属性）。
*   根据类型生成代码。

<UniversalEditor title="模板元编程示例" compare={true}>
```javascript !! js
// JavaScript: 编译时计算不是原生概念。
// 所有计算都是运行时的。
function factorial(n) {
  if (n === 0) return 1;
  return n * factorial(n - 1);
}

console.log(factorial(5)); // 120
```

```cpp !! cpp
// C++: 模板元编程 (编译时阶乘)
#include <iostream>

template <int N>
struct Factorial {
  static const int value = N * Factorial<N - 1>::value;
};

template <>
struct Factorial<0> {
  static const int value = 1;
};

int main() {
  // 编译时计算
  // std::cout << "5 的阶乘: " << Factorial<5>::value << std::endl; // 120
  return 0;
}
```
</UniversalEditor>

## 与 JavaScript 泛型的比较

JavaScript 没有像 C++ 模板那样的正式“泛型”系统。它的动态类型系统本身提供了一种泛型形式：

*   **动态类型：** 函数和类可以操作任何类型的值，而无需明确的类型参数。类型检查在运行时进行。
*   **灵活性：** 这提供了极大的灵活性并减少了样板代码。
*   **运行时错误：** 然而，与类型相关的错误在运行时捕获，而不是编译时。

相比之下，C++ 模板提供：
*   **编译时泛型：** 类型在编译时检查，从而更早地检测到错误并提高性能。
*   **类型安全：** 确保操作对于正在使用的类型是有效的。
*   **代码膨胀（潜在）：** 每个模板实例化都会生成单独的代码，这可能会增加可执行文件的大小。

## 模板最佳实践

1.  **保持简单：** 当需要真正的泛型时才使用模板；避免过度设计。
2.  **分离声明和定义（对于较大的模板）：** 对于类模板，通常将声明放在 `.h` 文件中，将定义放在 `.tpp` 或 `.hpp` 文件中，然后在 `.h` 文件的末尾包含 `.tpp`。对于函数模板，定义通常在头文件中。
3.  **使用 `typename` vs. `class`：** `typename` 通常更适合模板类型参数，尤其是在处理依赖类型时。
4.  **基于概念的约束 (C++20)：** 使用 C++20 Concepts 来约束模板参数，使模板错误更具可读性并提供更好的编译时检查。
5.  **避免过度元编程：** 虽然功能强大，但 TMP 可能会使代码难以阅读和调试。谨慎使用。

---

### 练习题：
1.  解释 C++ 中函数模板和类模板的用途。提供一个简单的示例。
2.  何时会使用模板特化？描述一个有益的场景。
3.  C++ 的模板系统与 JavaScript 实现泛型的方式有何不同？讨论每种方法的优缺点。

### 项目构想：
*   在 C++ 中实现一个通用的 `Stack` 类模板，它可以存储任何数据类型的元素。包括 `push`、`pop`、`top` 和 `isEmpty` 等方法。演示其与 `int`、`double` 和 `std::string` 类型的用法。